/* Copyright (c) Microsoft Corporation.
   Licensed under the MIT License. */

/***************************************************************************
	Author: ShonK
	Project: Kauai
	Reviewed:
	Copyright (c) Microsoft Corporation

	Basic command classes: CEX (command dispatcher), CMH (command handler).

	The command dispatcher (CEX) has a command (CMD) queue and a list of
	command handlers (CMH). During normal operation (CEX::FDispatchNextCmd),
	the CEX takes the next CMD from the queue, passes it to each CMH in its
	list (by calling CMH::FDoCmd) until one of the handlers returns true.
	If none of the handlers in the list returns true, the command is passed
	to the handler specified in the CMD itself (cmd.pcmh, if not nil).

	A CMH is placed in the handler list by a call to CEX::FAddCmh. The
	cmhl parameter determines the order of CMH's in the list. The grfcmm
	parameter indicates which targets the CMH wants to see commands for.
	The options are fcmmThis, fcmmNobody, fcmmOthers.

	The CEX class supports command stream recording and playback.

***************************************************************************/
#include "frame.h"
ASSERTNAME


// command map shared by every command handler
BEGIN_CMD_MAP_BASE(CMH)
END_CMD_MAP_NIL()


RTCLASS(CMH)
RTCLASS(CEX)


long CMH::_hidLast;


#ifdef DEBUG
/***************************************************************************
	Assert the validity of a CMD.
***************************************************************************/
void CMD::AssertValid(ulong grf)
{
	AssertThisMem();
	AssertNilOrPo(pgg, 0);
	AssertNilOrPo(pcmh, 0);
}
#endif //DEBUG


/***************************************************************************
	Static method to return a hid (command handler ID) such that the numbers
	{ hid, hid + 1, ... , hid + ccmh - 1 } are not currently in use and all
	have their high bit set. To avoid possible conflicts, hard-wired
	handler ID's should have their high bit clear. This guarantees that
	the values will not be returned again in the near future (whether or not
	they are in use).

	This calls vpappb->PcmhFromHid to determine if a hid is in use. This
	means that the returned hid is only unique over handlers that the
	application class knows about.
***************************************************************************/
long CMH::HidUnique(long ccmh)
{
	AssertIn(ccmh, 1, 1000);
	long ccmhT;

	_hidLast |= 0x80000000L;
	for (ccmhT = ccmh; ccmhT-- > 0; )
		{
		_hidLast++;
		if (!(_hidLast & 0x80000000L))
			{
			_hidLast = 0x80000000L;
			ccmhT = ccmh;
			continue;
			}
		if (pvNil != vpappb->PcmhFromHid(_hidLast))
			ccmhT = ccmh;
		}

	return _hidLast - (ccmh - 1);
}


/***************************************************************************
	Constructor for a command handler - set the handler id.
***************************************************************************/
CMH::CMH(long hid)
{
	AssertBaseThis(0);
	Assert(hid != hidNil, "bad hid");
	_hid = hid;
}


/***************************************************************************
	Destructor for a command handler - purge any global references to it
	from the app. The app purges it from the command dispatcher.
***************************************************************************/
CMH::~CMH(void)
{
	AssertThis(0);
	if (pvNil != vpappb)
		vpappb->BuryCmh(this);
}


#ifdef DEBUG
/***************************************************************************
	Assert the validity of a CMH.
***************************************************************************/
void CMH::AssertValid(ulong grf)
{
	CMH_PAR::AssertValid(0);
	Assert(_hid != hidNil, 0);
}
#endif //DEBUG


/***************************************************************************
	Protected virtual function to find a CMME (command map entry) for the
	given command id.
***************************************************************************/
bool CMH::_FGetCmme(long cid, ulong grfcmmWanted, CMME *pcmme)
{
	AssertThis(0);
	AssertVarMem(pcmme);
	Assert(cid != cidNil, "why is the cid nil?");
	CMM *pcmm;
	CMME *pcmmeT;
	CMME *pcmmeDef = pvNil;

	for (pcmm = Pcmm(); pcmm != pvNil; pcmm = pcmm->pcmmBase)
		{
		for (pcmmeT = pcmm->prgcmme; pcmmeT->cid != cidNil; pcmmeT++)
			{
			if (pcmmeT->cid == cid && (pcmmeT->grfcmm & grfcmmWanted))
				{
				*pcmme = *pcmmeT;
				return fTrue;
				}
			}

		// check for a default function
		if (pcmmeT->pfncmd != pvNil && (pcmmeT->grfcmm & grfcmmWanted) &&
				pcmmeDef == pvNil)
			{
			pcmmeDef = pcmmeT;
			}
		}

	// no specific one found, return the default one
	if (pcmmeDef != pvNil)
		{
		*pcmme = *pcmmeDef;
		return fTrue;
		}
	return fFalse;
}


/***************************************************************************
	Determines whether this command handler can handle the given command. If
	not, returns false (and does nothing else). If so, executes the command
	and returns true.

	NOTE: a true return indicates that the command was handled and should
	not be passed on down the command handler list - regardless of the
	success/failure of the execution of the command. Do not return false
	to indicate that command execution failed.
***************************************************************************/
bool CMH::FDoCmd(PCMD pcmd)
{
	AssertThis(0);
	AssertPo(pcmd, 0);
	CMME cmme;
	ulong grfcmm;

	if (pvNil == pcmd->pcmh)
		grfcmm = fcmmNobody;
	else if (this == pcmd->pcmh)
		grfcmm = fcmmThis;
	else
		grfcmm = fcmmOthers;

	if (!_FGetCmme(pcmd->cid, grfcmm, &cmme) || pvNil == cmme.pfncmd)
		return fFalse;

	return (this->*cmme.pfncmd)(pcmd);
}


/***************************************************************************
	Determines whether the command is enabled. If this command handler
	doesn't normally handle the command, this returns false (and does
	nothing else). Otherwise sets the grfeds and returns true.
***************************************************************************/
bool CMH::FEnableCmd(PCMD pcmd, ulong *pgrfeds)
{
	AssertThis(0);
	AssertPo(pcmd, 0);
	AssertVarMem(pgrfeds);
	CMME cmme;
	ulong grfcmm;

	if (pvNil == pcmd->pcmh)
		grfcmm = fcmmNobody;
	else if (this == pcmd->pcmh)
		grfcmm = fcmmThis;
	else
		grfcmm = fcmmOthers;

	if (!_FGetCmme(pcmd->cid, grfcmm, &cmme) || pvNil == cmme.pfncmd)
		return fFalse;

	if (cmme.pfneds == pvNil)
		{
		if (cidNil == cmme.cid)
			return fFalse;
		*pgrfeds = fedsEnable;
		}
	else if (!(this->*cmme.pfneds)(pcmd, pgrfeds))
		return fFalse;

	return fTrue;
}


/***************************************************************************
	Command dispatcher constructor.
***************************************************************************/
CEX::CEX(void)
{
	AssertBaseThis(0);
}


/***************************************************************************
	Destructor for a CEX.
***************************************************************************/
CEX::~CEX(void)
{
	AssertBaseThis(0);
	CMD cmd;
	long icmd;

	if (pvNil != _pglcmd)
		{
		for (icmd = _pglcmd->IvMac(); icmd-- != 0; )
			{
			_pglcmd->Get(icmd, &cmd);
			ReleasePpo(&cmd.pgg);
			}
		ReleasePpo(&_pglcmd);
		}

	ReleasePpo(&_pglcmhe);
	ReleasePpo(&_pcfl);
	ReleasePpo(&_pglcmdf);
	ReleasePpo(&_cmdCur.pgg);
}


/***************************************************************************
	Static method to create a new CEX object.
***************************************************************************/
PCEX CEX::PcexNew(long ccmdInit, long ccmhInit)
{
	AssertIn(ccmdInit, 0, kcbMax);
	AssertIn(ccmhInit, 0, kcbMax);
	PCEX pcex;

	if (pvNil == (pcex = NewObj CEX))
		return pvNil;

	if (!pcex->_FInit(ccmdInit, ccmhInit))
		ReleasePpo(&pcex);

	AssertNilOrPo(pcex, 0);
	return pcex;
}


/***************************************************************************
	Initialization of the command dispatcher.
***************************************************************************/
bool CEX::_FInit(long ccmdInit, long ccmhInit)
{
	AssertBaseThis(0);
	AssertIn(ccmdInit, 0, kcbMax);
	AssertIn(ccmhInit, 0, kcbMax);

	if (pvNil == (_pglcmd = GL::PglNew(size(CMD), ccmdInit)) ||
			pvNil == (_pglcmhe = GL::PglNew(size(CMHE), ccmhInit)))
		{
		return fFalse;
		}

	AssertThis(0);
	return fTrue;
}


/***************************************************************************
	Start recording a macro to the given chunky file.
***************************************************************************/
void CEX::Record(PCFL pcfl)
{
	AssertThis(0);
	AssertPo(pcfl, 0);

	if (_rs != rsNormal)
		{
		Bug("already recording or playing");
		return;
		}

	_rs = rsRecording;
	_rec = recNil;
	_pcfl = pcfl;
	_pcfl->AddRef();
	_cno = cnoNil;
	_icmdf = 0;
	_chidLast = 0;
	_cact = 0;
	Assert(_pglcmdf == pvNil, "why isn't _pglcmdf nil?");

	if ((_pglcmdf = GL::PglNew(size(CMDF), 100)) == pvNil)
		_rec = recMemError;
	else if (!_pcfl->FAdd(0, kctgMacro, &_cno))
		{
		_rec = recFileError;
		_cno = cnoNil;
		}
}


/***************************************************************************
	Stop recording a command stream, and write the command stream to
	file. If there were any errors, delete the command stream from the
	chunky file. Pushes a command notifying the world that recording
	has stopped. The command (cidCexRecordDone) contains the error code
	(rec), and cno in the first two lw's of the command. If the
	rec is not recNil, the cno is cnoNil and wasn't actually created.
***************************************************************************/
void CEX::StopRecording(void)
{
	AssertThis(0);
	BLCK blck;

	if (_rs != rsRecording)
		return;

	if (_rec == recNil)
		{
		long cb;

		if (_cact > 1)
			{
			// rewrite the last one's _cact
			CMDF cmdf;

			_pglcmdf->Get(_icmdf, &cmdf);
			cmdf.cact = _cact;
			_pglcmdf->Put(_icmdf, &cmdf);
			}
		cb = _pglcmdf->CbOnFile();
		if (!_pcfl->FPut(cb, kctgMacro, _cno, &blck) ||
				!_pglcmdf->FWrite(&blck))
			{
			_rec = recFileError;
			}
		}

	if (_rec != recNil && _cno != cnoNil)
		{
		_pcfl->Delete(kctgMacro, _cno);
		_cno = cnoNil;
		}

	if (_cno != cnoNil)
		{
		if (_pcfl->FSave(kctgFramework))
			_pcfl->SetTemp(fFalse);
		else
			{
			_rec = recFileError;
			_cno = cnoNil;
			}
		}
	_rs = rsNormal;
	ReleasePpo(&_pglcmdf);
	ReleasePpo(&_pcfl);

	PushCid(cidCexRecordDone, pvNil, pvNil, _rec, _cno);
}


/***************************************************************************
	Record a command.
***************************************************************************/
void CEX::RecordCmd(PCMD pcmd)
{
	AssertThis(0);
	AssertPo(pcmd, 0);
	Assert(_rs == rsRecording, "not recording");
	CMDF cmdf;

	if (_rec != recNil)
		return;

	if (_cact > 0)
		{
		if (_cmd.pgg == pvNil && FEqualRgb(pcmd, &_cmd, size(_cmd)))
			{
			// commands are the same, just increment the _cact
			if (pcmd->cid < cidMinNoRepeat || pcmd->cid >= cidLimNoRepeat)
				_cact++;
			return;
			}

		// new command is not the same as the previous one
		if (_cact > 1)
			{
			//rewrite the previous one's _cact
			_pglcmdf->Get(_icmdf, &cmdf);
			cmdf.cact = _cact;
			_pglcmdf->Put(_icmdf, &cmdf);
			}

		// increment _icmdf
		_icmdf++;
		_cact = 0;
		}

	// fill in the cmdf and save it in the list
	cmdf.cid = pcmd->cid;
	cmdf.hid = pcmd->pcmh == pvNil ? hidNil : pcmd->pcmh->Hid();
	cmdf.cact = 1;
	cmdf.chidGg = pcmd->pgg != pvNil ? ++_chidLast : 0;
	CopyPb(pcmd->rglw, cmdf.rglw, kclwCmd * size(long));

	if (!_pglcmdf->FInsert(_icmdf, &cmdf))
		{
		//out of memory
		_rec = recMemError;
		return;
		}

	// write the group and make it a child of the macro
	if (pvNil != pcmd->pgg)
		{
		BLCK blck;
		long cb;
		CNO cno;

		cb = pcmd->pgg->CbOnFile();
		if (!_pcfl->FAddChild(kctgMacro, _cno, cmdf.chidGg,
				cb, kctgGg, &cno, &blck))
			{
			goto LFileError;
			}
		if (!pcmd->pgg->FWrite(&blck))
			{
			_pcfl->DeleteChild(kctgMacro, _cno, kctgGg, cno, cmdf.chidGg);
LFileError:
			_pglcmdf->Delete(_icmdf);
			_rec = recFileError;
			return;
			}
		}
	_cact = 1;
	_cmd = *pcmd;
}


/***************************************************************************
	Play back the command stream starting in the given pcfl with the given
	cno.
***************************************************************************/
void CEX::Play(PCFL pcfl, CNO cno)
{
	AssertThis(0);
	AssertPo(pcfl, 0);
	BLCK blck;
	short bo, osk;

	if (_rs != rsNormal)
		{
		Bug("already recording or playing");
		return;
		}

	_rs = rsPlaying;
	_rec = recNil;
	_pcfl = pcfl;
	_pcfl->AddRef();
	_cno = cno;
	_icmdf = 0;
	_cact = 0;
	Assert(_pglcmdf == pvNil, "why isn't _pglcmdf nil?");

	if (!_pcfl->FFind(kctgMacro, _cno, &blck) ||
		(_pglcmdf = GL::PglRead(&blck, &bo, &osk)) == pvNil)
		{
		_rec = recFileError;
		StopPlaying();
		}
	else if (bo != kboCur || osk != koskCur)
		{
		_rec = recWrongPlatform;
		StopPlaying();
		}
}


/***************************************************************************
	Stop play back of a command stream. Pushes a command notifying the
	world that play back has stopped. The command (cidCexPlayDone) contains
	the error code (rec), and cno in the first two lw's of the command.
***************************************************************************/
void CEX::StopPlaying(void)
{
	AssertThis(0);

	if (_rs != rsPlaying)
		return;

	if (_rec == recNil && (_cact > 0 || _icmdf < _pglcmdf->IvMac()))
		_rec = recAbort;

	PushCid(cidCexPlayDone, pvNil, pvNil, _rec, _cno);

	_rs = rsNormal;
	ReleasePpo(&_pcfl);
	ReleasePpo(&_pglcmdf);
}


/***************************************************************************
	Read the next command.
***************************************************************************/
bool CEX::_FReadCmd(PCMD pcmd)
{
	AssertThis(0);
	AssertVarMem(pcmd);
	Assert(_rs == rsPlaying, "not playing a command stream");
	AssertPo(_pglcmdf, 0);
	CMDF cmdf;

	if (_cact > 0)
		{
		//this command is being repeated
		*pcmd = _cmd;
		_cact--;
		return fTrue;
		}
	if (_icmdf >= _pglcmdf->IvMac())
		goto LStop;

	_pglcmdf->Get(_icmdf, &cmdf);

	ClearPb(pcmd, size(*pcmd));
	pcmd->cid = cmdf.cid;
	pcmd->pcmh = vpappb->PcmhFromHid(cmdf.hid);
	pcmd->pgg = pvNil;
	CopyPb(cmdf.rglw, pcmd->rglw, kclwCmd * size(long));

	if (cmdf.chidGg != 0)
		{
		BLCK blck;
		KID kid;
		short bo, osk;

		Assert(cmdf.cact <= 1, 0);

		//read the gg
		if (!_pcfl->FGetKidChidCtg(kctgMacro, _cno, cmdf.chidGg,
				kctgGg, &kid) ||
			!_pcfl->FFind(kid.cki.ctg, kid.cki.cno, &blck) ||
			pvNil == (pcmd->pgg = GG::PggRead(&blck, &bo, &osk)))
			{
			_rec = recFileError;
			goto LStop;
			}
		if (bo != kboCur || osk != koskCur)
			{
			//don't know how to change byte order or translate strings
			ReleasePpo(&pcmd->pgg);
			_rec = recWrongPlatform;
			goto LStop;
			}
		}
	AssertPo(pcmd, 0);

	_icmdf++;
	if ((_cact = cmdf.cact - 1) > 0)
		_cmd = *pcmd;
	return fTrue;

	//error handling
LStop:
	StopPlaying();
	return fFalse;
}


/***************************************************************************
	Determine whether it's OK to communicate with the CMH. Default is to
	return true iff there is no current modal gob or the cmh is not a gob
	or it is a gob in the tree of the modal gob.
***************************************************************************/
bool CEX::_FCmhOk(PCMH pcmh)
{
	AssertNilOrPo(pcmh, 0);
	PGOB pgob;

	if (pvNil == _pgobModal || pvNil == pcmh || !pcmh->FIs(kclsGOB))
		return fTrue;

	for (pgob = (PGOB)pcmh; pgob != _pgobModal; pgob = pgob->PgobPar())
		{
		if (pvNil == pgob)
			return fFalse;
		}

	return fTrue;
}


/***************************************************************************
	Add a command handler to the filter list. These command handlers get
	a crack at every command whether or not it is for them. grfcmm
	determines which targets the handler will see commands for (as in
	command map entries). The cmhl is a command handler level - indicating
	the priority of the command handler. Handlers with lower cmhl values
	get first crack at commands. It is legal for a handler to be in the
	list more than once (even with the same cmhl value).
***************************************************************************/
bool CEX::FAddCmh(PCMH pcmh, long cmhl, ulong grfcmm)
{
	AssertThis(0);
	AssertPo(pcmh, 0);
	CMHE cmhe;
	long icmhe;

	if (fcmmNil == (grfcmm & kgrfcmmAll))
		{
		// no sense adding this
		Bug("why is grfcmm nil?");
		return fFalse;
		}

	if (!_FCmhOk(pcmh))
		return fFalse;

	_FFindCmhl(cmhl, &icmhe);
	cmhe.pcmh = pcmh;
	cmhe.cmhl = cmhl;
	cmhe.grfcmm = grfcmm;
	if (!_pglcmhe->FInsert(icmhe, &cmhe))
		return fFalse;
	if (icmhe <= _icmheNext)
		_icmheNext++;
	return fTrue;
}


/***************************************************************************
	Removes the the handler (at the given cmhl level) from the handler list.
***************************************************************************/
void CEX::RemoveCmh(PCMH pcmh, long cmhl)
{
	AssertThis(0);
	AssertPo(pcmh, 0);
	long icmhe, ccmhe;
	CMHE cmhe;

	if (!_FFindCmhl(cmhl, &icmhe))
		return;

	for (ccmhe = _pglcmhe->IvMac(); icmhe < ccmhe; icmhe++)
		{
		_pglcmhe->Get(icmhe, &cmhe);
		if (cmhe.cmhl != cmhl)
			break;
		if (cmhe.pcmh == pcmh)
			{
			_pglcmhe->Delete(icmhe);
			if (icmhe < _icmheNext)
				_icmheNext--;
			break;
			}
		}
}


/***************************************************************************
	Remove all references to the handler from the command dispatcher,
	including from the handler list and the command queue.
***************************************************************************/
void CEX::BuryCmh(PCMH pcmh)
{
	AssertThis(0);
	Assert(pcmh != pvNil, 0);
	long icmhe, icmd;
	CMHE cmhe;
	CMD cmd;

	if (_pgobModal == pcmh)
		_pgobModal = pvNil;

	if (_pgobTrack == pcmh)
		{
#ifdef WIN
		if (hNil != _hwndCapture && GetCapture() == _hwndCapture)
			ReleaseCapture();
		_hwndCapture = hNil;
#endif //WIN
		_pgobTrack = pvNil;
		}
	if (_cmdCur.pcmh == pcmh)
		_cmdCur.pcmh = pvNil;
	if (_cmd.pcmh == pcmh)
		{
		_cmd.pcmh = pvNil;
		_cact = 0;
		}

	for (icmhe = _pglcmhe->IvMac(); icmhe-- != 0; )
		{
		_pglcmhe->Get(icmhe, &cmhe);
		if (cmhe.pcmh == pcmh)
			{
			_pglcmhe->Delete(icmhe);
			if (icmhe < _icmheNext)
				_icmheNext--;
			}
		}

	for (icmd = _pglcmd->IvMac(); icmd-- != 0; )
		{
		_pglcmd->Get(icmd, &cmd);
		if (cmd.pcmh == pcmh)
			{
			_pglcmd->Delete(icmd);
			ReleasePpo(&cmd.pgg);
			}
		}
}


/***************************************************************************
	Finds the first item with the given cmhl in the handler list. If there
	aren't any, still sets *picmhe to where they would be.
***************************************************************************/
bool CEX::_FFindCmhl(long cmhl, long *picmhe)
{
	AssertThis(0);
	AssertVarMem(picmhe);
	long icmhe, icmheMin, icmheLim;
	CMHE *qrgcmhe;

	qrgcmhe = (CMHE *)_pglcmhe->QvGet(0);
	for (icmheMin = 0, icmheLim = _pglcmhe->IvMac(); icmheMin < icmheLim; )
		{
		icmhe = (icmheMin + icmheLim) / 2;
		if (qrgcmhe[icmhe].cmhl < cmhl)
			icmheMin = icmhe + 1;
		else
			icmheLim = icmhe;
		}

	*picmhe = icmheMin;
	return icmheMin < _pglcmhe->IvMac() && qrgcmhe[icmheMin].cmhl == cmhl;
}


/***************************************************************************
	Adds a command to the tail of the queue.
***************************************************************************/
void CEX::EnqueueCid(long cid, PCMH pcmh, PGG pgg,
	long lw0, long lw1, long lw2, long lw3)
{
	Assert(cid != cidNil, 0);
	AssertNilOrPo(pcmh, 0);
	AssertNilOrPo(pgg, 0);
	CMD cmd;

	cmd.cid = cid;
	cmd.pcmh = pcmh;
	cmd.pgg = pgg;
	cmd.rglw[0] = lw0;
	cmd.rglw[1] = lw1;
	cmd.rglw[2] = lw2;
	cmd.rglw[3] = lw3;
	EnqueueCmd(&cmd);
}


/***************************************************************************
	Pushes a command onto the head of the queue.
***************************************************************************/
void CEX::PushCid(long cid, PCMH pcmh, PGG pgg,
	long lw0, long lw1, long lw2, long lw3)
{
	Assert(cid != cidNil, 0);
	AssertNilOrPo(pcmh, 0);
	AssertNilOrPo(pgg, 0);
	CMD cmd;

	cmd.cid = cid;
	cmd.pcmh = pcmh;
	cmd.pgg = pgg;
	cmd.rglw[0] = lw0;
	cmd.rglw[1] = lw1;
	cmd.rglw[2] = lw2;
	cmd.rglw[3] = lw3;
	PushCmd(&cmd);
}


/***************************************************************************
	Adds a command to the tail of the queue. This asserts if it can't add
	it to the queue. Clients should make sure that the value of ccmdInit
	passed to PcexNew is large enough to handle the busiest session.
***************************************************************************/
void CEX::EnqueueCmd(PCMD pcmd)
{
	AssertThis(0);
	AssertPo(pcmd, 0);
	Assert(pcmd->cid != cidNil, "why enqueue a nil command?");

	if (!_pglcmd->FEnqueue(pcmd))
		{
		Bug("event queue not big enough");
		ReleasePpo(&pcmd->pgg);
		}
#ifdef DEBUG
	if (_ccmdMax < _pglcmd->IvMac())
		_ccmdMax = _pglcmd->IvMac();
#endif //DEBUG
}


/***************************************************************************
	Pushes a command onto the head of the queue. This asserts if it can't
	add it to the queue. Clients should make sure that the value of ccmdInit
	passed to PcexNew is large enough to handle the busiest session.
***************************************************************************/
void CEX::PushCmd(PCMD pcmd)
{
	AssertThis(0);
	AssertPo(pcmd, 0);
	Assert(pcmd->cid != cidNil, "why enqueue a nil command?");

	if (!_pglcmd->FPush(pcmd))
		{
		Bug("event queue not big enough");
		ReleasePpo(&pcmd->pgg);
		}
#ifdef DEBUG
	if (_ccmdMax < _pglcmd->IvMac())
		_ccmdMax = _pglcmd->IvMac();
#endif //DEBUG
}


/***************************************************************************
	Checks if a cid is in the queue.
***************************************************************************/
bool CEX::FCidIn(long cid)
{
	AssertThis(0);
	Assert(cid != cidNil, "why check for a nil command?");

	long icmd;
	CMD cmd;

	for (icmd = _pglcmd->IvMac(); icmd-- > 0; )
		{
		_pglcmd->Get(icmd, &cmd);
		if (cmd.cid == cid)
			return fTrue;
		}

	return fFalse;
}


/***************************************************************************
	Flushes all instances of a cid in the queue.
***************************************************************************/
void CEX::FlushCid(long cid)
{
	AssertThis(0);
	Assert(cid != cidNil, "why flush a nil command?");

	long icmd;
	CMD cmd;

	for (icmd = _pglcmd->IvMac(); icmd-- > 0; )
		{
		_pglcmd->Get(icmd, &cmd);
		if (cmd.cid == cid)
			{
			_pglcmd->Delete(icmd);
			ReleasePpo(&cmd.pgg);
			}
		}
}


/***************************************************************************
	Get the next command to be dispatched (put it in _cmdCur). Return tYes
	if there was a command and it should be dispatched. Return tNo if there
	wasn't a command (if the system queue should be checked). Return tMaybe
	if the command shouldn't be dispatched, but we shouldn't check the
	system queue.
***************************************************************************/
bool CEX::_TGetNextCmd(void)
{
	AssertThis(0);

	// get the next command from the command stream
	if (!_pglcmd->FPop(&_cmdCur))
		{
		ClearPb(&_cmdCur, size(_cmdCur));
		if (pvNil == _pgobTrack)
			return tNo;

		AssertPo(_pgobTrack, 0);
		PT pt;
		PCMD_MOUSE pcmd = (PCMD_MOUSE)&_cmdCur;

		_cmdCur.pcmh = _pgobTrack;
		_cmdCur.cid = cidTrackMouse;
		vpappb->TrackMouse(_pgobTrack, &pt);
		pcmd->xp = pt.xp;
		pcmd->yp = pt.yp;
		pcmd->grfcust = vpappb->GrfcustCur();

		if (!vpappb->FForeground())
			{
			// if we're not in the foreground, toggle the state of
			// fcustMouse repeatedly. Most of the time, this will cause
			// the client to stop tracking the mouse. Clients
			// whose tracking state depends on something other than
			// the mouse state should call vpappb->FForeground() to
			// determine if tracking should be aborted.
			static bool _fDown;

			_fDown = !_fDown;
			if (!_fDown)
				pcmd->grfcust ^= fcustMouse;
			else
				pcmd->grfcust &= ~fcustMouse;
			}
		}
	AssertPo(&_cmdCur, 0);

	// handle playing and recording
	if (rsPlaying == _rs)
		{
		// We're playing back. Throw away the incoming command and play
		// one from the stream.
		ReleasePpo(&_cmdCur.pgg);

		// let a cidCexStopPlay go through and handle it at the end.
		// this is so a cmh can intercept it.
		if (_cmdCur.cid != cidCexStopPlay && !_FReadCmd(&_cmdCur))
			return tMaybe;
		}

	if (!_FCmhOk(_cmdCur.pcmh))
		{
		vpappb->BadModalCmd(&_cmdCur);
		ReleasePpo(&_cmdCur.pgg);
		return tMaybe;
		}

	return tYes;
}


/***************************************************************************
	Send the command (_cmdCur) to the given command handler.
***************************************************************************/
bool CEX::_FSendCmd(PCMH pcmh)
{
	AssertPo(pcmh, 0);

	if (!_FCmhOk(pcmh))
		return fFalse;

	return pcmh->FDoCmd(&_cmdCur);
}


/***************************************************************************
	Handle post processing on the command - record it if we're recording,
	free the pgg, etc.
***************************************************************************/
void CEX::_CleanUpCmd(void)
{
	// If the handler went away during command dispatching, we should
	// have heard about it (via BuryCmh) and should have set _cmdCur.pcmh
	// to nil.
	AssertNilOrPo(_cmdCur.pcmh, 0);

	// record the command after dispatching, in case arguments got added
	// or the cid was set to nil.
	if (rsRecording == _rs && !FIn(_cmdCur.cid, cidMinNoRecord, cidLimNoRecord)
			&& cidNil != _cmdCur.cid && cidCexStopRec != _cmdCur.cid)
		{
		RecordCmd(&_cmdCur);
		}

	// check for a stop record or stop play command
	if (rsNormal != _rs)
		{
		if (cidCexStopPlay == _cmdCur.cid && rsPlaying == _rs)
			StopPlaying();
		else if (cidCexStopRec == _cmdCur.cid && rsRecording == _rs)
			StopRecording();
		}

	ReleasePpo(&_cmdCur.pgg);
}


/***************************************************************************
	If there is a command in the queue, this dispatches it and returns
	true. If there aren't any commands in the queue, it simply returns
	false. If a gob is tracking the mouse and the queue is empty, a
	cidTrackMouse command is generated and dispatched to the gob.

	NOTE: care has to be taken here because a CMH may go away while
	dispatching the command. That's why _cmdCur and _icmheNext are
	member variables - so BuryCmh can adjust them if needed.
***************************************************************************/
bool CEX::FDispatchNextCmd(void)
{
	AssertThis(0);
	CMHE cmhe;
	bool fHandled;
	bool tRet;

	if (_fDispatching)
		{
		Bug("recursing into FDispatchNextCmd!");
		return fFalse;
		}
	_fDispatching = fTrue;

	tRet = _TGetNextCmd();
	if (tYes != tRet)
		{
		_fDispatching = fFalse;
		return tRet != tNo;
		}

	// pipe it through the command handlers, then to the target
	fHandled = fFalse;
	for (_icmheNext = 0; _icmheNext < _pglcmhe->IvMac() && !fHandled; )
		{
		_pglcmhe->Get(_icmheNext++, &cmhe);
		if (pvNil == _cmdCur.pcmh)
			{
			if (!(cmhe.grfcmm & fcmmNobody))
				continue;
			}
		else if (cmhe.pcmh == _cmdCur.pcmh)
			{
			if (!(cmhe.grfcmm & fcmmThis))
				continue;
			}
		else if (!(cmhe.grfcmm & fcmmOthers))
			continue;

		fHandled = _FSendCmd(cmhe.pcmh);
		}

	if (!fHandled && pvNil != _cmdCur.pcmh)
		{
		AssertPo(_cmdCur.pcmh, 0);
		fHandled = _FSendCmd(_cmdCur.pcmh);
		}

	_CleanUpCmd();

	_fDispatching = fFalse;
	return fTrue;
}


/***************************************************************************
	Give the handler a crack at enabling/disabling the command.
***************************************************************************/
bool CEX::_FEnableCmd(PCMH pcmh, PCMD pcmd, ulong *pgrfeds)
{
	AssertPo(pcmh, 0);
	AssertPo(pcmd, 0);
	AssertVarMem(pgrfeds);

	if (!_FCmhOk(pcmh))
		return fFalse;

	return pcmh->FEnableCmd(pcmd, pgrfeds);
}


/***************************************************************************
	Determines whether the given command is currently enabled. This is
	normally used for menu graying/checking etc and toolbar enabling/status.
***************************************************************************/
bool CEX::GrfedsForCmd(PCMD pcmd)
{
	AssertThis(0);
	AssertPo(pcmd, 0);
	long icmhe, ccmhe;
	CMHE cmhe;
	ulong grfeds;

	// pipe it through the command handlers, then to the target
	for (icmhe = 0, ccmhe = _pglcmhe->IvMac(); icmhe < ccmhe; icmhe++)
		{
		_pglcmhe->Get(icmhe, &cmhe);
		grfeds = fedsNil;
		if (_FEnableCmd(cmhe.pcmh, pcmd, &grfeds))
			goto LDone;
		}
	if (pcmd->pcmh != pvNil)
		{
		AssertPo(pcmd->pcmh, 0);
		grfeds = fedsNil;
		if (_FEnableCmd(pcmd->pcmh, pcmd, &grfeds))
			goto LDone;
		}

	// handle the CEX commands
	switch (pcmd->cid)
		{
	case cidCexStopRec:
		grfeds = rsRecording == _rs ? fedsEnable : fedsDisable;
		break;

	case cidCexStopPlay:
		grfeds = rsPlaying == _rs ? fedsEnable : fedsDisable;
		break;

	default:
		grfeds = fedsDisable;
		break;
		}

LDone:
	return grfeds;
}


/***************************************************************************
	Determines whether the given command is currently enabled. This is
	normally used for menu graying/checking etc and toolbar enabling/status.
***************************************************************************/
bool CEX::GrfedsForCid(long cid, PCMH pcmh, PGG pgg,
	long lw0, long lw1, long lw2, long lw3)
{
	AssertThis(0);
	Assert(cid != cidNil, 0);
	AssertNilOrPo(pcmh, 0);
	AssertNilOrPo(pgg, 0);
	CMD cmd;

	cmd.cid = cid;
	cmd.pcmh = pcmh;
	cmd.pgg = pgg;
	cmd.rglw[0] = lw0;
	cmd.rglw[1] = lw1;
	cmd.rglw[2] = lw2;
	cmd.rglw[3] = lw3;
	return GrfedsForCmd(&cmd);
}


/***************************************************************************
	See if the next command is a key command and if so, put it in *pcmd
	(and remove it from the queue).
***************************************************************************/
bool CEX::FGetNextKey(PCMD pcmd)
{
	AssertThis(0);
	AssertVarMem(pcmd);
	long iv;

	if (_rs != rsNormal)
		goto LFail;
	if ((iv = _pglcmd->IvMac()) > 0)
		{
		//get next cmd
		_pglcmd->Get(iv - 1, pcmd);
		if (pcmd->cid == cidKey)
			{
			AssertDo(_pglcmd->FPop(pvNil), 0);
			return fTrue;
			}
LFail:
		TrashVar(pcmd);
		return fFalse;
		}

	return vpappb->FGetNextKeyFromOsQueue((PCMD_KEY)pcmd);
}


/***************************************************************************
	The given GOB wants to track the mouse.
***************************************************************************/
void CEX::TrackMouse(PGOB pgob)
{
	AssertThis(0);
	AssertPo(pgob, 0);
	Assert(_pgobTrack == pvNil, "some other gob is already tracking the mouse");

	_pgobTrack = pgob;
#ifdef WIN
	_hwndCapture = pgob->HwndContainer();
	SetCapture(_hwndCapture);
#endif //WIN
}


/***************************************************************************
	Stop tracking the mouse.
***************************************************************************/
void CEX::EndMouseTracking(void)
{
	AssertThis(0);

#ifdef WIN
	if (pvNil != _pgobTrack)
		{
		if (hNil != _hwndCapture && GetCapture() == _hwndCapture)
			ReleaseCapture();
		_hwndCapture = hNil;
		}
#endif //WIN
	_pgobTrack = pvNil;
}


/***************************************************************************
	Return the gob that is tracking the mouse.
***************************************************************************/
PGOB CEX::PgobTracking(void)
{
	AssertThis(0);
	return _pgobTrack;
}


/***************************************************************************
	Suspend or resume the command dispatcher. All this does is
	release (capture) the mouse if we're current tracking the mouse and
	we're being suspended (resumed).
***************************************************************************/
void CEX::Suspend(bool fSuspend)
{
	AssertThis(0);

#ifdef WIN
	if (pvNil == _pgobTrack || hNil == _hwndCapture)
		return;

	if (fSuspend && GetCapture() == _hwndCapture)
		ReleaseCapture();
	else if (!fSuspend && GetCapture() != _hwndCapture)
		SetCapture(_hwndCapture);
#endif //WIN
}


/***************************************************************************
	Set the modal GOB.
***************************************************************************/
void CEX::SetModalGob(PGOB pgob)
{
	AssertThis(0);
	AssertNilOrPo(pgob, 0);

	_pgobModal = pgob;
}


#ifdef DEBUG
/***************************************************************************
	Assert the validity of the command dispatcher
***************************************************************************/
void CEX::AssertValid(ulong grf)
{
	CEX_PAR::AssertValid(fobjAllocated);
	AssertPo(_pglcmhe, 0);
	AssertPo(_pglcmd, 0);
	AssertNilOrPo(_pglcmdf, 0);
	AssertNilOrPo(_pcfl, 0);
	AssertNilOrPo(_cmdCur.pgg, 0);
}


/***************************************************************************
	Mark the memory associated with the command dispatcher.
***************************************************************************/
void CEX::MarkMem(void)
{
	AssertThis(0);
	CMD cmd;
	long icmd;

	CEX_PAR::MarkMem();
	MarkMemObj(_pglcmhe);
	MarkMemObj(_pglcmd);
	MarkMemObj(_pglcmdf);
	MarkMemObj(_cmdCur.pgg);

	for (icmd = _pglcmd->IvMac(); icmd-- != 0; )
		{
		_pglcmd->Get(icmd, &cmd);
		if (cmd.pgg != pvNil)
			MarkMemObj(cmd.pgg);
		}
}
#endif //DEBUG


